Scriptable Objects
Im Dungeon-Crawler-Genre spielt das Einsammeln und Verwenden von Gegenständen eine zentrale Rolle. Auch in unserem Spiel müssen Gegenstände strategisch genutzt werden um den Dungeon zu überleben. Für die Verwaltung der vielen Gegenstände haben wir jedoch keine klassische SQL-Datenbank benötigt. Stattdessen haben wir uns für Scriptable Objects (SO) entschieden. Das sind Datencontainer, die unabhängig von Szenen oder Objekten funktionieren (d.h. man kann diese auf der Festplatte speichern und im Spiel instanziieren). Sie eignen sich perfekt, um eine große Menge an Itemdaten übersichtlich zu speichern (z.B. Waffen, Rüstungen oder Essen).
Aufbau eines Items
Für jeden Item-Typ haben wir ein eigenes Scriptable Object erstellt und über das AssetMenu
verfügbar gemacht. Dadurch konnten wir Items direkt im Editor konfigurieren, inklusive Icons, Name, Beschreibung und Werten.
[CreateAssetMenu(menuName = "Create Items/Item")]
public class InventoryItemData : ScriptableObject
{
public enum Rarity {Common, Uncommon, Rare, Epic, Legendary };
[Header("Set UI Attributes")]
public Sprite Icon;
public int MaxStackSize;
public int ID = -1;
public string DisplayName;
[TextArea(4, 4)]
public string Description;
public Rarity rarity = Rarity.Common;
[Header("Set Item Attributes")]
public GameObject ItemPrefab;
public InventoryItemType ItemType;
[Range(0,100)]
public int dropChance;
public bool consumable = false;
public bool isAbility = false;
public float goldValue = 1;
}
Loot-System und Item-Datenbank
Die Scriptable Objects dienen nicht nur zur Anzeige, sondern auch zur Steuerung des Loot-Verhaltens. Gegner und Schatztruhen können ihre Items direkt aus einer Item-Datenbank beziehen, die als einfache Liste von SOs aufgebaut ist.
- Jeder Gegner wählt zufällig bis zu fünf Items aus der Liste aus, die beim Tod fallen können
- Alternativ kann man gezielt Items pro Gegner-Typ definieren
- Neue Items werden automatisch erkannt und zur Datenbank hinzugefügt
Design pattern
Damit Items im Spiel unterschiedliche Effekte haben können, haben wir das Strategy Pattern verwendet. Mit dem Strategy Pattern können wir für jedes Item ein eigenes Verhalten definieren, ohne den restlichen Code anpassen zu müssen.
Beim Benutzen eines Items wird die Methode UseItem()
aufgerufen. Was dabei passiert, wird im jeweiligen Skript festgelegt (z.B. heilen oder Waffe ausrüsten). Die Logik bleibt dadurch flexibel, und wir vermeiden if-Abfragen wie if(itemType == "Weapon")
. So können wir neue Itemtypen einfach hinzufügen, ohne bestehende Systeme zu ändern.
[CreateAssetMenu(menuName = "Create Items/Item")]
public class ItemDataProvider : MonoBehavior
{
//ItemData
public virtual void UseItem()
{
Debug.Log($"Using {DisplayName}")
}
}
Zusätzlich gibt es einen Wrapper zur Verbindung zwischen Item und UI bzw. GameObject:
[CreateAssetMenu(menuName = "Create Items/Item")]
public class ItemDataProvider : MonoBehavior
{
[SerializeField] public InventoryItemData item;
public InventoryItemData Item => item;
}
Vorteile von Scriptable Objects
Scriptable Objects machen unser Projekt übersichtlicher und leichter zu erweitern.
- Item-Parameter lassen sich anpassen ohne Code zu ändern
- Neue Items können direkt im Editor erstellt werden
- Items lassen sich unabhängig vom Inventarsystem verwalten
Ein Spieldesigner kann ganz einfach ein neues Scriptable Object erstellen, es mit Werten füllen und per ItemDataProvider
in die Spielszene einbinden. Falls ein passendes 3D-Modell noch fehlt, lässt sich dieses separat in Blender erstellen und dem Objekt direkt zuweisen.
Abbildung Game Scene mit und ohne Scriptable Objects